/*
 * Copyright (c) 2014 Luca Agostini, Alessandro Cantini, Alessandro Dignani
 *
 * This file is part of MeteoCal.
 *
 * MeteoCal is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.polimi.se2.agostini_cantini_dignani.meteocal.business;

import it.polimi.se2.agostini_cantini_dignani.meteocal.entity.Calendar;
import it.polimi.se2.agostini_cantini_dignani.meteocal.entity.Token;
import it.polimi.se2.agostini_cantini_dignani.meteocal.entity.User;
import it.polimi.se2.agostini_cantini_dignani.meteocal.util.HashUtils;
import org.apache.commons.lang3.time.DateUtils;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Date;
import java.util.Hashtable;
import java.util.Map;

/**
 * Enterprise bean that manages the business logic related to users.
 *
 * @author Alessandro Cantini
 */
@Stateless
public class UserManager {

	private static final Long RESEND_WAIT_INTERVAL = DateUtils.MILLIS_PER_HOUR;

	private static final Map<User, Long> reSendMap = new Hashtable<>();

	@PersistenceContext
	private EntityManager em;

	@Inject
	private Principal principal;

	@EJB
	private EmailManager emm;

	/**
	 * Creates a new user in the database
	 *
	 * @param user the user to be created
	 */
	public void createUser(User user) {
		createUser(user, false);
	}

	public void createUser(User user, boolean sendConfirmationEmail) {
		user.setGroupName(User.GROUP_NAME);
		Calendar calendar = new Calendar();
		user.setCalendar(calendar);
		Token token = new Token();
		token.setTokenString(HashUtils.createToken(user));
		token.setUser(user);
		user.setToken(token);
		em.persist(user);
		if (sendConfirmationEmail) {
			emm.sendTokenEmail(user);
		}
	}

	public void editUser(User user) {
		User old = user;
		em.merge(user);
		old = user;
	}

	public void confirmEmail(String token) throws ConfirmationException {
		User loggedUser = getLoggedUser();
		if (loggedUser.hasConfirmedEmail()) {
			throw new IllegalArgumentException("The user has already confirmed the email");
		}
		if (loggedUser.getToken().getTokenString().equals(token)) {
			loggedUser.setToken(null);
			editUser(loggedUser);
			reSendMap.remove(loggedUser);
		} else {
			throw new ConfirmationException();
		}
	}

	/**
	 * Returns the user that is logged at a specific moment
	 *
	 * @return the logged user
	 */
	public User getLoggedUser() {
		return em.find(User.class, principal.getName());
	}

	/**
	 * Returns the user with a specified email. If the email doesn't correspond to any user, null is
	 * returned
	 *
	 * @param email the email of the user
	 * @return the user with a specified email
	 */
	public User findByEmail(String email) {
		if (email == null) {
			return null;
		}
		return em.find(User.class, email);
	}

	public void reSendTokenEmail(User user) throws FrequentReSendEmailException {
		Long lastRequest = reSendMap.get(user);
		long now = System.currentTimeMillis();
		if (lastRequest != null && now - lastRequest < RESEND_WAIT_INTERVAL) {
			throw new FrequentReSendEmailException(new Date(lastRequest + RESEND_WAIT_INTERVAL));
		}
		reSendMap.put(user, now);
		emm.sendTokenEmail(user);
	}

	public void login(FacesContext context, String email, String password) throws ServletException {
		HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
		logout(context, false);
		request.login(email, password);
	}

	public void logout(FacesContext context, boolean invalidateSession) throws ServletException {
		if (invalidateSession) {
			context.getExternalContext().invalidateSession();
		}
		HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
		request.logout();
	}

	public class ConfirmationException extends Exception {
		public ConfirmationException() {
			super();
		}
	}

	public class FrequentReSendEmailException extends Exception {
		private final Date availableDate;

		public FrequentReSendEmailException(Date availableDate) {
			super();
			this.availableDate = availableDate;
		}

		public Date getAvailableDate() {
			return availableDate;
		}
	}
}
